#include "InSdkSetNtpTime.h"

#define DEF_NTP_PORT 123
#define DEF_NTP_JAN_1970 0x83aa7e80
#define DEF_NTP_FRAC(x) (4294 * (x) + ((1981 * (x)) >> 11))
#define DEF_NTP_USEC(x) (((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16))
#define DEF_NTP_DATA(i) ntohl(((unsigned int *)data)[i])
#define DEF_NTP_LI 0
#define DEF_NTP_VN 3
#define DEF_NTP_MODE 3
#define DEF_NTP_STRATUM 0
#define DEF_NTP_POLL 4
#define DEF_NTP_PREC (-6)

typedef struct ntptime
{
    unsigned int coarse;
    unsigned int fine;
} SComNtpTime;

void InSdkNtp_SendPacket(int fd)
{
    unsigned int data[12];
    struct timeval now;

    if (sizeof(data) != 48)
    {
        fprintf(stderr, "size error\n");
        return;
    }

    memset((char *)data, 0, sizeof(data));
    data[0] = htonl((DEF_NTP_LI << 30) | (DEF_NTP_VN << 27) | (DEF_NTP_MODE << 24) | (DEF_NTP_STRATUM << 16) | (DEF_NTP_POLL << 8) | (DEF_NTP_PREC & 0xff));
    data[1] = htonl(1 << 16);
    data[2] = htonl(1 << 16);
    gettimeofday(&now, NULL);
    data[10] = htonl(now.tv_sec + DEF_NTP_JAN_1970);
    data[11] = htonl(DEF_NTP_FRAC(now.tv_usec));
    send(fd, data, 48, 0);
}

int InSdkNtp_SetDateTime(int tzoffsetsecond, time_t gettimesecond)
{
    time_t setnow;
    struct timeval tv;
    struct tm *utctm;
    int ret = 0;
    printf("set dev datetime function!!\n");
    // 转化为UTC时间
    utctm = gmtime(&gettimesecond);

    // 根据时区差值，设置时间（融入设定时区）
    utctm->tm_hour = utctm->tm_hour + ((tzoffsetsecond / 60 / 60));
    utctm->tm_min = utctm->tm_min + ((-1) * ((tzoffsetsecond / 60) % 60));
    // printf("tzoffsetsecond==%d, utctm->tm_hour==%d\n", tzoffsetsecond, utctm->tm_hour);

    // 转化为与1970-1-1 0:0:0的秒差值（融入设定时区）
    setnow = mktime(utctm);
    // 检查时间正确与否
    if (setnow == (gettimesecond + (-1) * tzoffsetsecond))
    {
        printf("setnow:%ld, utctm->tm_hour :%d utctm->tm_min:%d\n", setnow, utctm->tm_hour, utctm->tm_min);
    }
    else
    {
        printf("npt setting time is ignored setnow:%ld, utctm->tm_hour :%d utctm->tm_min:%d\n", setnow, utctm->tm_hour, utctm->tm_min);
    }
    // printf("setnow:%ld\n", setnow);

    // 设日期时间
    tv.tv_sec = setnow;
    tv.tv_usec = 0;
    if ((ret = settimeofday(&tv, (struct timezone *)0)) < 0)
    {
        printf("---settime error: \'%d\'---+++++++strerror:%s+++++++\n", ret, strerror(errno));
    }

    return ret;
}

void InSdkNtp_GetNewTime(unsigned int *data, struct timeval *new_time)
{
    SComNtpTime trantime;
    trantime.coarse = DEF_NTP_DATA(10);
    trantime.fine = DEF_NTP_DATA(11);

    new_time->tv_sec = trantime.coarse - DEF_NTP_JAN_1970;
    new_time->tv_usec = DEF_NTP_USEC(trantime.fine);

    printf("\n\n%s %d: new_time %lu %lu \n", __func__, __LINE__, new_time->tv_sec, new_time->tv_usec);
}

int InSdkNtp_GetTime(char *pHostName, struct timeval *pNtpTime)
{
    int sockfd;
    struct sockaddr_in addr_src, addr_dst;
    fd_set fds;
    int ret;
    int recv_len;
    unsigned int buf[12];
    int addr_len;
    int failed = 0;

    struct timeval timeout;

    addr_len = sizeof(struct sockaddr_in);

    memset(&addr_src, 0, addr_len);
    addr_src.sin_family = AF_INET;
    addr_src.sin_addr.s_addr = htonl(INADDR_ANY);
    addr_src.sin_port = htons(DEF_NTP_PORT);

    struct hostent *host;
    memset(&addr_dst, 0, addr_len);
    addr_dst.sin_family = AF_INET;
    printf("%s %d: HostName = %s\n", __func__, __LINE__, pHostName);

    host = gethostbyname(pHostName);
    if (NULL == host || host->h_addr_list == NULL || host->h_addr_list[0] == NULL)
    {
        printf("gethostbyname fail\n");
        return -1;
    }

    memcpy(&(addr_dst.sin_addr.s_addr), host->h_addr_list[0], 4);
    addr_dst.sin_port = htons(DEF_NTP_PORT);

    if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
    {
        printf("create socket error!\n");
        return -1;
    }

    int reuse = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&reuse, sizeof(int)) < 0)
    {
        printf("setsockopt SO_REUSEADDR error! error code = %d,err string = %s\n", errno, strerror(errno));
        return -1;
    }

    ret = bind(sockfd, (struct sockaddr *)&addr_src, addr_len);
    if (-1 == ret)
    {
        printf("bind error!\n");
        close(sockfd);
        return -1;
    }

    ret = connect(sockfd, (struct sockaddr *)&addr_dst, addr_len);
    if (-1 == ret)
    {
        printf("connect error!\n");
        close(sockfd);
        return -1;
    }
    while (1)
    {
        FD_ZERO(&fds);
        FD_SET(sockfd, &fds);

        timeout.tv_sec = 1;
        timeout.tv_usec = 0;
        ret = select(sockfd + 1, &fds, NULL, NULL, &timeout);
        if (0 == ret || !FD_ISSET(sockfd, &fds))
        {
            failed++;
            if (failed > 8)
            {
                close(sockfd);
                return -1;
            }
            InSdkNtp_SendPacket(sockfd);
            printf("InSdkNtp_SendPacket(sockfd);\n");
            continue;
        }
        recv_len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr_dst,
                            (socklen_t *)&addr_len);
        if (-1 == recv_len)
        {
            printf("recvfrom error\n");
            close(sockfd);
            return -1;
        }
        else if (recv_len > 0)
        {
            InSdkNtp_GetNewTime(buf, pNtpTime);
            break;
        }
    }

    close(sockfd);

    return 0;
}

int Ext_InSdkNtp_SetTime(int tzoffset, char *ntpserver)
{
    int ret = -1;
    time_t now = 0;

    struct timeval TimeSet;
    struct tm *pTime;
    if (0 != InSdkNtp_GetTime(ntpserver, &TimeSet))
    {
        printf("get ntp time fail\n");
        return -1;
    }

    time_t timer;
    timer = (long long)TimeSet.tv_sec;
    pTime = gmtime(&timer); /* 时间是格林威治时间，不是北京时间, 北京时间则加 8 */
    printf("## %d-%02d-%02d %02d:%02d:%02d ##\n", pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday, pTime->tm_hour, pTime->tm_min, pTime->tm_sec);
    if (settimeofday(&TimeSet, NULL) < 0)
    {
        printf("settimeofdaynfail\n");
    }

    time(&now);
    ret = InSdkNtp_SetDateTime(tzoffset, now);

    return ret;
}
